# -*- coding: utf-8 -*-
# @Time    : 2023/11/3 9:24
# @Author  : floraachy
# @File    : base_page
# @Software: PyCharm
# @Desc: Playwright UI自动化基础操作封装


# 标准库导入
import os
from typing import Union, Pattern
from loguru import logger
# 第三方库导入
import allure
from playwright.sync_api import TimeoutError as TError
from playwright.sync_api import Page
from playwright.sync_api import expect


class BasePage:
    """
    Playwright UI自动化基础操作封装
    """

    def __init__(self, page: Page):
        self.page = page

    # --------------------------------- 操作 ---------------------------------#
    @allure.step("____访问页面，路由：{url}")
    def visit(self, url: str) -> None:
        """
        访问页面
        :param url: url
        """
        self.page.goto(url)
        self.page.wait_for_load_state()
        logger.info(f"____访问页面，路由：{url}")

    @allure.step("____刷新页面")
    def refresh(self) -> None:
        """
        刷新页面
        """
        self.page.reload()
        logger.info(f"____刷新页面")

    @allure.step("____点击元素， 元素定位：{locator}")
    def click(self, locator: str) -> None:
        """
        点击操作
        :param locator: 元素定位
        """
        self.page.click(locator)
        logger.info(f"____点击元素， 元素定位：{locator}")

    @allure.step("____勾选元素， 元素定位： {locator}")
    def check(self, locator: str) -> None:
        """
        勾选checkbox
        :param locator: 元素定位
        """
        self.page.check(locator)
        logger.info(f"____勾选元素， 元素定位：{locator}")

    @allure.step("____取消勾选元素， 元素定位： {locator}")
    def uncheck(self, locator: str) -> None:
        """
        取消勾选checkbox
        :param locator: 元素定位
        """
        self.page.uncheck(locator)
        logger.info(f"____取消勾选元素， 元素定位： {locator}")

    @allure.step("____鼠标悬浮在元素上，元素定位： {locator}")
    def hover(self, locator: str) -> None:
        """
        悬浮在某元素上
        :param locator: 元素定位
        """
        self.page.hover(locator)
        logger.info(f"____鼠标悬浮在元素上，元素定位： {locator}")

    @allure.step("____输入内容： {text}， 元素定位： {locator}")
    def input(self, locator: str, text: str) -> None:
        """
        输入内容
        :param locator: 元素定位
        :param text: 输入的内容
        """
        self.click(locator)
        self.page.fill(selector=locator, value=text)
        logger.info(f"____输入内容： {text}， 元素定位： {locator}")

    @allure.step("____键盘键入内容： {text}， 元素定位： {locator}")
    def type(self, locator: str, text: str) -> None:
        """
        模拟键盘的操作，键入内容
        :param locator: 元素定位
        :param text: 输入的内容
        """
        self.click(locator)
        self.page.type(selector=locator, text=text)
        logger.info(f"____键盘键入内容： {text}， 元素定位： {locator}")

    @allure.step("____选择选项： {option}， 元素定位： {locator}")
    def select_option(self, locator: str, option: str) -> None:
        """
        选择option
        :param locator: 元素定位
        :param option: 选项内容
        """
        self.page.select_option(selector=locator, value=option)
        logger.info(f"____选择选项： {option}， 元素定位： {locator}")

    @allure.step("____上传文件： {file_path}， 元素定位： {locator}")
    def upload_file(self, locator: str, file_path: str) -> None:
        """
        上传文件
        :param locator: 元素定位
        :param file_path: 文件路径
        """
        if os.path.isfile(file_path):
            self.page.set_input_files(selector=locator, files=file_path)
            self.page.wait_for_timeout(1000)
            logger.info(f"____上传文件： {file_path}， 元素定位： {locator}")
        else:
            logger.error(f"____上传文件失败，附件未找到，请检查{file_path}下是否存在该文件")
            raise ValueError(f"____上传文件失败，附件未找到，请检查{file_path}下是否存在该文件")

    @allure.step("____执行js脚本： {js}")
    def execute_js(self, js, *args) -> None:
        """
        执行javascript脚本
        :param js: javascript脚本
        """
        self.page.evaluate(js, *args)
        logger.info(f"____执行js脚本： {js}")

    @allure.step("____按{keyboard}键， 元素定位： {locator}")
    def press(self, locator: str, keyboard: str) -> None:
        """
        :param locator: 元素定位
        :param keyboard: 键
        """
        self.page.press(locator, keyboard)
        logger.info(f"____按{keyboard}键， 元素定位： {locator}")

    @allure.step("____截图， 全屏={full_page}， 元素定位： {locator}， 图片保存路径：{path}")
    def screenshot(self, path, full_page=True, locator=None):
        """截图功能，默认截取全屏，如果传入定位器表示截取元素"""
        if locator is not None:
            self.page.locator(locator).screenshot(path=path)
            logger.info(f"____截图， 全屏={full_page}， 元素定位： {locator}， 图片保存路径：{path}")
            return path
        self.page.screenshot(path=path, full_page=full_page)
        logger.info(f"____截图， 全屏={full_page}， 元素定位： {locator}， 图片保存路径：{path}")
        return path

    # --------------------------------- 查询 ---------------------------------#

    @allure.step("____获取所有的元素 - {locator}")
    def get_all_elements(self, locator: str) -> Union[list, None]:
        """
         获取所有符合定位的元素
        :param locator: 元素定位
        :return: 元素/None
        """
        try:
            elems = self.page.query_selector_all(locator)
            logger.info(f"____获取所有的元素， 元素定位： {locator}， 元素：{elems}")
            return elems
        except Exception as e:
            logger.error(f"____获取所有的元素失败， 元素定位： {locator}，报错信息：{e} ")
            return None

    @allure.step("____获取元素文本值， 元素定位： {locator}")
    def get_text(self, locator: str) -> Union[str, None]:

        """
        获取元素的文本内容
        :param locator: 元素定位
        :return: 文本值/None
        """
        try:
            text_value = self.page.locator(locator).text_content()
            logger.info(f"____获取元素文本值， 元素定位： {locator}, 文本值： {text_value}")
            return text_value
        except Exception as e:
            logger.error(f"____获取元素文本值， 元素定位： {locator}，报错信息：{e} ")
            return None

    @allure.step("____获取所有符合定位要求的元素的文本内容， 元素定位： {locator}")
    def get_all_elements_text(self, locator) -> Union[list, None]:
        """
        获取所有符合定位要求的元素的文本内容
        :param locator: 元素定位
        :return: 文本值/None
        """
        try:
            elements = self.get_all_elements(locator)
            elems_text = [element.text_content() for element in elements]
            logger.info(f"____获取所有符合定位要求的元素的文本内容， 元素定位： {locator}, 文本内容：{elems_text}")
            return elems_text
        except Exception as e:
            logger.error(f"____获取所有符合定位要求的元素的文本内容， 元素定位： {locator}，报错信息：{e} ")
            return None

    @allure.step("____获取元素属性值， 元素定位： {locator}")
    def get_element_attribute(self, locator: str, attr_name: str) -> Union[str, None]:
        """
        获取元素属性值
        :param locator: 元素定位
        :param attr_name: 属性名称
        :return: 元素属性值
        """
        try:
            attr_name = self.page.locator(locator).get_attribute(name=attr_name)
            logger.info(f"____获取元素属性值， 元素定位： {locator}, 属性值：{attr_name}")
            return attr_name
        except Exception as e:
            logger.error(f"____获取元素属性值， 元素定位： {locator}，报错信息：{e} ")
            return None

    @allure.step("____获取内部文本值， 元素定位： {locator}")
    def get_inner_text(self, locator: str) -> Union[str, None]:
        """
        获取内部文本值
        :param locator: 元素定位
        :return: 内部文本值
        """
        try:
            text_value = self.page.inner_text(selector=locator)
            logger.info(f"____获取内部文本值， 元素定位： {locator}, 内部值：{text_value}")
            return text_value
        except Exception as e:
            logger.error(f"____获取内部文本值， 元素定位： {locator}，报错信息：{e} ")
            return None

    @allure.step("____获取html值， 元素定位： {locator}")
    def get_inner_html(self, locator: str) -> Union[str, None]:
        """
        获取html值
        :param locator: 元素定位
        :return: html值
        """
        try:
            html_value = self.page.inner_html(selector=locator)
            logger.info(f"____获取html值， 元素定位： {locator}, html值：{html_value}")
            return html_value
        except Exception as e:
            logger.error(f"____获取html值， 元素定位： {locator}，报错信息：{e} ")
            return None

    # --------------------------------- 断言 ---------------------------------#
    @allure.step("____元素 - {locator} 具有 - {text}")
    def have_text(self, locator: str, text: str) -> None:
        """
        断言：验证元素是否具有指定的文本内容
        :param locator: 元素定位
        :param text: 文本内容
        """
        logger.info(f"____元素 - {locator} 具有 - {text}")
        expect(self.page.locator(locator)).to_have_text(text)

    @allure.step("____元素 - {locator} 包含 - {text}")
    def contain_text(self, locator: str, text: str) -> None:
        """
        断言：验证元素是否包含指定的文本
        :param locator: 元素定位
        :param text: 文本内容
        """
        logger.info(f"____元素 - {locator} 包含 - {text}")
        expect(self.page.locator(locator)).to_contain_text(text)

    @allure.step("____网页标题 = {title}")
    def have_title(self, title: Union[str]) -> None:
        """
        断言：检查页面是否存在指定的标题；存在则通过，不存在则失败；
        :param title: 页面标题，接受一个字符串参数或正则表达式参数
        """
        logger.info(f"____网页标题 = {title}")
        expect(self.page).to_have_title(title)

    @allure.step("____网页标题 ！=  {title}")
    def not_have_title(self, title: str) -> None:
        """
        断言：检查页面是否存在指定的标题；不存在则通过，存在则失败；
        :param title: 页面标题，接受一个字符串参数或正则表达式参数
        """
        logger.info(f"____网页标题 ！=  {title}")
        expect(self.page).not_to_have_title(title)

    @allure.step("____页面路径是 = {url}")
    def have_url(self, url: Union[str]) -> None:
        """
        断言：检查页面是否存在指定的 URL；存在则通过，不存在则失败；
        :param url: 页面标题，接受一个字符串参数或正则表达式参数
        """
        logger.info(f"____页面路径是 = {url}")
        expect(self.page).to_have_url(url)

    @allure.step("____页面路径 ！= {url}")
    def not_have_url(self, url: str) -> None:
        """
        断言：检查页面是否存在指定的 URL；不存在则通过，存在则失败；
        :param url: 页面标题，接受一个字符串参数或正则表达式参数
        """
        logger.info(f"____页面路径 ！= {url}")
        expect(self.page).not_to_have_url(url)

    @allure.step("____元素 - {locator} 包含 - {value}")
    def is_element_have_value(self, locator: str, value: str) -> None:
        """
        断言：验证元素是否具有指定的值
        :param locator: 元素定位
        :param value: 指定值
        """
        logger.info(f"____元素 - {locator} 包含 - {value}")
        elem = self.page.locator(locator)
        expect(elem).to_have_value(value)

    @allure.step("____元素 - {locator} 属性 - {attr_name} 其值是 - {value}")
    def is_element_attr_have_value(self, locator: str, attr_name: str, value: str) -> None:
        """
        断言：验证元素的某个属性具有指定的值
        :param locator: 元素定位
        :param attr_name: 元素属性名称
        :param value: 文本内容
        """
        logger.info(f"____元素 - {locator} 属性 - {attr_name} 其值是 - {value}")
        assert value == self.get_element_attribute(locator=locator, attr_name=attr_name)

    @allure.step("____元素 - {locator} 被选中")
    def is_element_disabled(self, locator: str) -> None:
        """
        断言：验证复选框是否被选中
        :param locator: 元素定位
        """
        logger.info(f"____元素 - {locator} 被选中")
        elem = self.page.locator(locator)
        expect(elem).to_be_checked()

    @allure.step("____元素 - {locator} 被禁用")
    def is_element_disabled(self, locator: str) -> None:
        """
        断言：验证元素是否被禁用
        :param locator: 元素定位
        """
        logger.info(f"____元素 - {locator} 被禁用")
        elem = self.page.locator(locator)
        expect(elem).to_be_disabled()

    @allure.step("____元素 - {locator} 可编辑")
    def is_element_editable(self, locator: str) -> None:
        """
        断言：验证元素是否可编辑
        :param locator: 元素定位
        """
        logger.info(f"____元素 - {locator} 可编辑")
        elem = self.page.locator(locator)
        expect(elem).to_be_editable()

    @allure.step("____元素 - {locator} 容器为空")
    def is_element_empty(self, locator: str) -> None:
        """
        断言：验证容器是否为空
        :param locator: 元素定位
        """
        logger.info(f"____元素 - {locator} 容器为空")
        elem = self.page.locator(locator)
        expect(elem).to_be_empty()

    @allure.step("____元素 - {locator} 启用")
    def is_element_enabled(self, locator: str) -> None:
        """
        断言：验证元素是否启用
        :param locator: 元素定位
        """
        logger.info(f"____元素 - {locator} 启用")
        elem = self.page.locator(locator)
        expect(elem).to_be_enabled()

    @allure.step("____元素 - {locator} 聚焦")
    def is_element_focused(self, locator: str) -> None:
        """
        断言：验证元素是否获得焦点
        :param locator: 元素定位
        """
        logger.info(f"____元素 - {locator} 聚焦")
        elem = self.page.locator(locator)
        expect(elem).to_be_focused()

    @allure.step("____元素 - {locator} 隐藏")
    def is_element_hidden(self, locator: str) -> None:
        """
        断言：验证元素是否隐藏
        :param locator: 元素定位
        """
        logger.info(f"____元素 - {locator} 隐藏")
        elem = self.page.locator(locator)
        expect(elem).to_be_hidden()

    @allure.step("____元素 - {locator} 可见")
    def is_element_visible(self, locator: str) -> None:
        """
        断言：验证元素是否可见
        :param locator: 元素定位
        """
        logger.info(f"____元素 - {locator} 可见")
        elem = self.page.locator(locator)
        expect(elem).to_be_visible()

    @allure.step("____元素 - {locator} 具有属性 - {attr_name}")
    def is_element_have_attr(self, locator: str, attr_name: str) -> None:
        """
        断言：验证元素是否具有指定的属性
        :param locator: 元素定位
        :param attr_name: 预期元素属性名称
        """
        logger.info(f"____元素 - {locator} 具有属性 - {attr_name}")
        elem = self.page.locator(locator)
        expect(elem).to_have_attribute(attr_name)

    @allure.step("____元素 - {locator} 具有类 - {class_name}")
    def is_element_have_class(self, locator: str, class_name: str) -> None:
        """
        断言：验证元素是否具有指定的属性
        :param locator: 元素定位
        :param class_name: 预期类名称
        """
        logger.info(f"____元素 - {locator} 具有类 - {class_name}")
        elem = self.page.locator(locator)
        expect(elem).to_have_class(class_name)

    @allure.step("____元素 - {locator} 个数 - {elem_count}")
    def is_element_count(self, locator: str, elem_count: int) -> None:
        """
        断言：验证元素个数是否与期望值相等
        :param locator: 元素定位
        :param elem_count: 预期元素个数
        """
        logger.info(f"____元素 - {locator} 个数 - {elem_count} ")
        elem = self.page.locator(locator)
        expect(elem).to_have_count(elem_count)

    @allure.step("____元素 - {locator} 具有CSS - {css_value} ")
    def is_element_have_css(self, locator: str, css_value: Union[str, Pattern[str]]) -> None:
        """
        断言：验证元素个数是否与期望值相等
        :param locator: 元素定位
        :param css_value: css属性，接收str以及正则表达式， 例如"button"， 或者"display", "flex"
        """
        logger.info(f"____元素 - {locator} 具有CSS - {css_value}  ")
        elem = self.page.locator(locator)
        expect(elem).to_have_css(css_value)

    @allure.step("____元素 - {locator} 具有id - {id_name} ")
    def is_element_have_id(self, locator: str, id_name: str) -> None:
        """
        断言：验证元素是否具有指定的ID
        :param locator: 元素定位
        :param id_name: 元素id属性
        """
        logger.info(f"____元素 - {locator} 具有id - {id_name} ")
        elem = self.page.locator(locator)
        expect(elem).to_have_css(id_name)

    @allure.step("____元素 - {locator} 具有指定的JavaScript属性 - {js_value} ")
    def is_element_have_js_property(self, locator: str, js_value: str) -> None:
        """
        断言：用于验证元素是否具有指定的JavaScript属性
        :param locator: 元素定位
        :param js_value: 元素id属性
        """
        logger.info(f"____元素 - {locator} 具有指定的JavaScript属性 - {js_value} ")
        expect(locator).to_have_js_property(js_value)

    @allure.step("____元素 - {locator} 出现在DOM中")
    def is_element_attached(self, locator: str) -> bool:
        """
        判断元素是否出现在DOM中，出现返回true，否则返回false
        :param locator: 元素定位
        :return: True/False
        """
        try:
            self.page.wait_for_selector(selector=locator, state="attached")
            logger.info(f"____元素 - {locator} 出现在DOM中")
            return True
        except TError:
            logger.info(f"____元素 - {locator} 未出现在DOM中")
            return False

    @allure.step("____元素 - {locator} 从DOM中移除")
    def is_element_detached(self, locator: str) -> bool:
        """
        判断元素是否从DOM中移除，移除返回true，否则返回false
        :param locator: 元素定位
        :return: True/False
        """
        try:
            self.page.wait_for_selector(selector=locator, state="attached")
            logger.info(f"____元素 - {locator} 从DOM中移除")
            return True
        except TError:
            logger.info(f"____元素 - {locator} 未从DOM中移除")
            return False
